#!/bin/bash
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
#
#   jbk.snapshot beta 0.01
#   
#   Written 3/29/05 Aaron Morrison Original concept from Mike Rubel
#
#   BUGS: 1. -r hourly is currently the only interval that works
#         because I need to decide whether those snapshots are generated
#         by themselves, or they are derived from hourly.N snapshots and
#         if so, what's N
#         2. Snapshots are created in $SNAPSHOT_RW/$SNAPSHOT/$TYPE.INCR
#         ie /snapshots/home/hourly.1
#         and I am not sure this makes sense, or something like this
#         /snapshots/hourly.1/home
#
#   Modified: 10/3/06 Aaron Morrison
#      Incorporate new calling syntax of
#      $0 nh@a,b,c,d...nw@a,...nm@a,...[--ecludes, --includes] src_dir snap_dir
#      also to include --remote=user@host:/remote_snapshot/dir
#      --remount=(Yes|No) to enable/disable remount options
#      Logic here is to use netapp style syntax for configuration via crontab
#      # hourlys at times a,b,c,d...
#      # weeklys at time a
#      # monthlys at time a
#      excludes and includes
#      source dir
#      snapshot dir
#      remote host snapshot mirrors
#      remount option enable/disable
#     

#
# Help
#
give_help(){
   echo "Usage: $0 [-hgvVl] [-H nhourlys:6,12,18,24] [-N Nightlys:24hr-time] [-W Weeklys:24hr-time] [-M Monthlys:24hr-time]"
   echo "Usage: $0 [-hgvVl] [-i --includes] [-e --excludes] [-s --source-dir] [-d --snapshot-dir] [-rh --remote host@path/snapshot-dir]"
   echo "Usage: $0 [-hgvVl] [-rm --remount YES|NO]"
   echo "Options:"
   echo "   -h/?		Print this message."
   echo ""
   echo "   -g			Enable Bash Debugging."
   echo ""
   echo "   -v			Verbose."
   echo ""
   echo "   -V			Really Verbose."
   echo ""
   echo "   -H      Number of Hourlys to do at 24hr times."
   echo "           -H 6:4,8,12,16,20,24 is 6 hourlys/day at"
   echo "           4,8,12,16,20,24"
   echo ""
   echo "   -N      Number of Nightlys at Midnight"
   echo ""
   echo "   -W      Number of Weeklys at Midnight"
   echo ""
   echo "   -M      Number of Monthlys at Midnight"
   echo ""
   echo "   -i      --includes rsync style includes."
   echo ""
   echo "   -e      --exludes rsync style exludes."
   echo ""
   echo "   -s      Source directory(directory you want snapshots of)."
   echo ""
   echo "   -d      Snapshot directory(directory where to place snapshots)."
   echo ""
   echo "   -rh     Remote host spec for doing offsite snapshot mirror."
   echo "           host@path:/snapshot_dir"
   echo ""
   echo "   -rm     Remount option for snapshots on other block device."
   echo "           YES|NO."
   echo ""

}

#
# Attempt to remount the RW mount point as $MOUNT_OP; else abort
#
REMOUNT(){
      case $1 in
         ro   ) OPTION="ro";;
         rw   ) OPTION="rw";;
         *    ) echo "$0: Error Invalid option"; exit 1;;
      esac
   echo "mount -o remount,$OPTION $MOUNT_DEVICE $SNAPSHOT_RW"
   #mount -o remount,$OPTION $MOUNT_DEVICE $SNAPSHOT_RW
   #if [ $? ]; then
   #   echo "$0: Error could not remount $SNAPSHOT_RW $MOUNT_OP"
   #   exit 1
   #fi
}

#
# Rotate Snapshots
#
rotate(){
   #
   # Follow the original premise from http://www.mikerubel.org/computers/rsync_snapshots/ 
   #
   #   Step 1: Rubel deleted oldest if it existed, I think renaming it .pruneme is better...
   #           as we can come back later and prune.
   #   Step 2: Rotate Middle snapshots if they exist
   #   Step 3. Make a hard-link-only(except dirs) copy of the latest snapshot
   #   Step 4. Rsync from the system into the latest snapshot (notice that
   #           rsync behaves like cp --remove-destination by default, so the destination
   #           is unlinked first.  If it were not so, this would copy over the other
   #           snapshot(s) too!
   #   Step 5. Update mtime of hourly.0 to reflect snapshot time

   #
   # Step 1: mv oldest snapshot to .pruneme 
   #

   #
   # 2nd thinking...perhaps the way to do this is to have this script parse configs
   #   and each config file specifiies what filesystem to snapshot and at what level of
   #   of back ups.
   # Use of following convention suggested:
   #       Hourly.nM rotates out into Daily.n1
   #       Daily.nM rotates out into Weekly.n1
   #       Nightly.nM rotates out into Weekly.n2
   #       Weekly.nM rotates out into Monthly.n1
   #       Weekly.nM rotates over itself...
   #       Monthy.nM rotates into Level0.date.nM 
   #       Level0.date.nM is basis for incremental backups via tar for disaster recovery...
   #
   one(){
      if [ -d $SNAPSHOT_RW/$SNAPSHOT/$TYPE.$INCR ]; then
         #echo "mv -p $SNAPSHOT_RW/$SNAPSHOT/$TYPE.PruneMe"
         eval 'mv $SNAPSHOT_RW/$SNAPSHOT/$TYPE.$INCR $SNAPSHOT_RW/$SNAPSHOT/$TYPE.PruneMe'
      fi
      return
   }

   #
   #   Step 2: Rotate middle snapshots by one if they exist
   #           Mike used mv here, rsync could be better given the correct args
   #
   two(){
      declare -i TMPCOUNT
      declare -i ONE=1
      while [ $INCR -gt $ONE ]; do
         TMPCOUNT=$INCR-1
         if [ -d $SNAPSHOT_RW/$SNAPSHOT/$TYPE.$TMPCOUNT ]; then
            #echo "mv $SNAPSHOT_RW/$SNAPSHOT/$TYPE.$TMPCOUNT $SNAPSHOT_RW/$SNAPSHOT/$TYPE.$INCR"
            eval 'mv $SNAPSHOT_RW/$SNAPSHOT/$TYPE.$TMPCOUNT $SNAPSHOT_RW/$SNAPSHOT/$TYPE.$INCR'
        # special rsync args $SNAPSHOT_RW/$SNAPSHOT/hourly.$TMPCOUNT $SNAPSHOT_RW/$SNAPSHOT/hourly.$INCR
         fi
         INCR=$INCR-1
      done
      return
   }

   #
   #   Step 3: make a hard-link-only(except dirs) copy of the latest snapshot
   #
   three(){
      if [ -d $SNAPSHOT_RW/$SNAPSHOT/$TYPE.0 ]; then
         #echo "cp -al $SNAPSHOT_RW/$SNAPSHOT/$TYPE.0 $SNAPSHOT_RW/$TYPE.1"
         eval 'cp -al $SNAPSHOT_RW/$SNAPSHOT/$TYPE.0 $SNAPSHOT_RW/$SNAPSHOT/$TYPE.1'
      fi
      return
   }     
   
   #
   #   Step 4: rsync command
   #
   four(){
      #$RSYNC_CMD $DIR_to_Snapshot $SNAPSHOT_RW/$DIR_to_Snapshot/$STIME.$SNAPSHOTS
      #echo "rsync -va --delete --delete-excluded	--exclude-from="$EXCLUDES" \
      #    /$SNAPSHOT/ $SNAPSHOT_RW/$SNAPSHOT/$TYPE.0"
      eval 'rsync -av --delete --delete-excluded --exclude-from="$EXCLUDES" \
	    /$SNAPSHOT/ $SNAPSHOT_RW/$SNAPSHOT/$TYPE.0'
      return
   }

   #
   #   Step 5: Update mtime of hourly.0 to reflect snapshot time
   #
   five(){
      #echo "$TOUCH $SNAPSHOT_RW/$SNAPSHOT/$TYPE.0"
      $TOUCH $SNAPSHOT_RW/$SNAPSHOT/$TYPE.0
      return
   }
   case $1 in
      hourly   ) declare -i INCR=$HOURLY; \
                 TYPE="hourly"; \
                 one; two; three; four; five;;
      nightly  ) declare -i INCR=$NIGHTY; \
                 TYPE="nightly"; \
                 one; two; three;;
      weekly   ) declare -i INCR=$WEEKLY; \
                 TYPE="weekly"; \
                 one; two; three;;
      monthly  ) declare -i INCR=$MONTHLY; \
                 TYPE="monthly"; \
                 one; two; three;;
      *        ) exit 1;;
   esac
   return
}

#
# Prune
#
prune (){
   if [ -d $SNAPSHOT_RW ] ; then
       REMOUNT rw
       echo "/usr/bin/find $SNAPSHOT_RW \( -atime +30 -a -name *.PruneMe \) -ls -exec /bin/rm {} \;"
       #/usr/bin/find $SNAPSHOT_RW \( -atime +30 -a -name *.PruneMe \) -ls -exec /bin/rm {} \;
       REMOUNT ro
   else
       echo "$0: $SNAPSHOT_RW doesn't exist!!!";
   fi
   return
}

#
# Got r00t?
#
if [ $UID != "0" ]; then 
   echo "Sorry, must be root.  Exiting..."; 
   exit 1; 
fi

#
# Vars
#
IFS=
unset PATH
alias echo=/bin/echo
alias mount=/bin/mount
alias rm=/bin/rm
alias mv=/bin/mv
alias cp=/bin/cp
alias touch=/bin/touch
alias rsync=/usr/bin/rsync
MOUNT_DEVICE=/dev/sda5
SNAPSHOT=etc
SNAPSHOT_RW=/data1/snapshot
EXCLUDES=/usr/local/etc/snapshot_exclude
#INCLUDES=/usr/local/etc/backup_include
HOURLY=4
DAILY=2
NIGHTLY=2
WEEKLY=4
MONTHLY=1
#RYSNC_CMD=rsync --archive --delete --delete-excluded {$VERBOSE} -e{$SSH} --includes{$INCLUDES} --excludes{$EXCLUDES}

#
# Main
#
while getopts "hgvVpr:" opt; do
   case $opt in
      h*  ) give_help; exit 1;;
      \?  ) give_help; exit 1;;
      d   ) SNAPSHOT=$OPTARG;;
      e   ) EXCLUDES=$OPTARG;;
      g   ) DEBUG="true";;
      v   ) VERBOSE="-v";;
      s   ) SOURCEDIR=$OPTARG;;
      V   ) VERBOSE="-vvv";;
			H   ) HOURLY=$OPTARG;;
      N   ) NIGHTLY=$OPTARG;;
      W   ) WEEKLY=$OPTARG;;
      M   ) MONTHLY=$OPTARG;; 
      p   ) PRUNE="TRUE";;
      rh  ) RHOST=$OPTARG;;
      rm  ) RMOUNT=$OPTARG;;
   esac
done
shift $(($OPTIND -1))

#
# Dubugging
#
if [ -n "$DEBUG" ]; then
   echo "DEBUGGING CALLED"
   set -o xtrace
fi

#
# Hourly Magic
#
echo HOURLY|awk -F "[:,]" '{print NF}'
#
# Snapshot
#
if [ -z $PRUNE ]; then
   case $INT in
      hourly  ) REMOUNT rw; rotate hourly; REMOUNT ro; exit;;
      nightly ) REMOUNT rw; rotate nightly; REMOUNT ro; exit;;
      weekly  ) REMOUNT rw; rotate weekly; REMOUNT ro; exit;;
      monthly ) REMOUNT rw; rotate monthly; REMOUNT ro; exit;;
      *       ) give_help; exit 1;;
   esac
fi

#
# Prune
#
if [ -n $PRUNE ]; then
   prune
fi
